#include "CreateHill.h"
#include "SharedConstants.h"
#include "HeightMapCoordinates.h"

#include <math.h>
#include <vector>
#include <map>

namespace
  {
  /*
  * @params
  * bool i_change - we really set heightmap coordinates or just calculating them
  * size_t o_recursion_depth - information on which level of recursion we are now. Should be initialised as 0.
  * HeightMap& io_HeightMap - height map 
  * size_t i_base_x - x coord of point which is centre of our algo
  * size_t i_base_y - y coord of point which is centre of our algo
  * size_t i_x - x coord of point now
  * size_t i_y - y coord of point now
  * EDirections i_direction - creation hill/hole/none
  * double i_base_heightchange - base change of height
  * std::vector<std::vector<bool>>& i_visited - matrix for visited points (no dublicates of function calls)
  * std::vector<TVector3>& o_changed_points - coords of point that we have changed
  */

  void _SearchPointsWithBFSAlgo(std::vector<TVector3>& o_changed_points, bool i_change, size_t& o_recursion_depth, HeightMap& io_HeightMap, size_t i_base_x, size_t i_base_y, size_t i_x, size_t i_y, EDirections i_direction, double i_base_heightchange, std::vector<std::vector<bool>>& i_visited)
    {
    if(i_x > io_HeightMap.GetSizeX() || i_y > io_HeightMap.GetSizeY())
      return;

    i_visited[i_x][i_y] = true;

    static bool max_range = false;
    if(max_range == false)
      ++o_recursion_depth;

    //Directions
    //x + dx[0] ; y + dy[0] - Direction 'Down'
    //x + dx[1] ; y + dy[1] - Direction 'Right'
    //x + dx[2] ; y + dy[2] - Direction 'Up'
    //x + dx[3] ; y + dy[3] - Direction 'Left'
    int dx[4] = {1, 0, -1, 0};
    int dy[4] = {0, 1, 0, -1};

    size_t x_dist = abs(static_cast<int>(i_x - i_base_x));
    size_t y_dist = abs(static_cast<int>(i_y - i_base_y));
    size_t distance = x_dist + y_dist;
    if(distance > o_recursion_depth)
      {
      max_range = false;
      return;
      }

    double height_change = i_direction * HeightMapUtils::CalculateHeightChangeWithGaussDistribution(i_x, i_y, i_base_x, i_base_y, i_base_heightchange);
    if(fabs(height_change) < GroundConstants::DELTA)
      {
      max_range = true;
      return;  
      }

    double new_heightmap_value = io_HeightMap.Get(i_x, i_y) + height_change;
    if(i_change == true)
      {
      if (new_heightmap_value * GroundConstants::CELL_SIZE > GroundConstants::MAX_HILL_HEIGHT)
        new_heightmap_value = GroundConstants::MAX_HILL_HEIGHT / GroundConstants::CELL_SIZE;
      if (new_heightmap_value * GroundConstants::CELL_SIZE < GroundConstants::MAX_HOLE_DEPTH)
        new_heightmap_value = GroundConstants::MAX_HOLE_DEPTH / GroundConstants::CELL_SIZE;

      io_HeightMap.Set(i_x, i_y, new_heightmap_value );
      }
    //adding point coords to o_changed_points
    o_changed_points.push_back(HeightMapUtils::ConvertHMapPointToTVector(i_x, i_y, new_heightmap_value));

    for(auto k = 0; k < 4; ++k)
      {
      size_t next_coord_x = i_x + dx[k];
      size_t next_coord_y = i_y + dy[k];

      if(next_coord_x < io_HeightMap.GetSizeX() && next_coord_y < io_HeightMap.GetSizeY())
        {
        if(i_visited[next_coord_x][next_coord_y] == false)
          {
          _SearchPointsWithBFSAlgo(o_changed_points, i_change, o_recursion_depth, io_HeightMap, i_base_x, i_base_y, next_coord_x, next_coord_y, i_direction, i_base_heightchange, i_visited);
          }
        }
      }
    max_range = false;
    }

  void _Find_n_Change(std::vector<TVector3>& o_changed_points, HeightMap& io_HeightMap, size_t i_base_x, size_t i_base_y, size_t i_x, size_t i_y, EDirections i_direction, double i_base_heightchange, std::vector<std::vector<bool>>& i_visited)
    {
    size_t recursion_depth = 0;
    _SearchPointsWithBFSAlgo(o_changed_points, true, recursion_depth, io_HeightMap, i_base_x, i_base_y, i_x, i_y, i_direction, i_base_heightchange, i_visited);
    }
  }

namespace HeightMapUtils
  {
  void GetPointsThatWillBeChanged(std::vector<TVector3>& o_changed_points, HeightMap& io_HeightMap, size_t i_base_x, size_t i_base_y, size_t i_x, size_t i_y, EDirections i_direction, double i_base_heightchange, std::vector<std::vector<bool>>& i_visited)
    {
    size_t recursion_depth = 0;
    _SearchPointsWithBFSAlgo(o_changed_points, false, recursion_depth, io_HeightMap, i_base_x, i_base_y, i_x, i_y, i_direction, i_base_heightchange, i_visited);
    }

  double CalculateHeightChangeWithGaussDistribution (size_t i_x, size_t i_y, size_t i_x_base, size_t i_y_base, double i_base_heightchange)
    {   
    double first_part_of_power = (pow(abs(static_cast<int>(i_x - i_x_base)), 2)) / (2 * pow(GroundConstants::GAUSSIAN_SIGMA_X, 2));
    double second_part_of_power =(pow(abs(static_cast<int>(i_y - i_y_base)), 2)) / (2 * pow(GroundConstants::GAUSSIAN_SIGMA_Y, 2));

    double power_of_exponent = first_part_of_power + second_part_of_power;

    double result = i_base_heightchange / exp(power_of_exponent);
    return result;
    }

  void CreateHill(HeightMap& io_HeightMap, size_t i_coordinate_x, size_t i_coordinate_y, double i_base_heightchange, EDirections i_direction)
    {		
    std::vector<std::vector<bool>> visited;

    visited.reserve(io_HeightMap.GetSizeX());
    for(size_t i = 0; i < io_HeightMap.GetSizeX(); ++i)
      {
      std::vector<bool> tmp(io_HeightMap.GetSizeY(), false);
      visited.push_back(tmp);
      }

    std::vector<TVector3> changed_points;    
    _Find_n_Change(changed_points, io_HeightMap, i_coordinate_x, i_coordinate_y, i_coordinate_x, i_coordinate_y, i_direction, i_base_heightchange, visited);
    }


  void GetTrianglesThatWillBeChanged(Model3D& o_changed_triangles, HeightMap& i_height_map, TVector2 i_point, double i_height_changed, EDirections i_direction)
    {
    std::vector<std::vector<bool>> visited;

    visited.reserve(i_height_map.GetSizeX());
    for(size_t i = 0; i < i_height_map.GetSizeX(); ++i)
      {
      std::vector<bool> tmp(i_height_map.GetSizeY(), false);
      visited.push_back(tmp);
      }

    auto hm_coords = HeightMapUtils::FindHeightMapIndexes(i_point, TVector2(GroundConstants::ORIGIN_X, GroundConstants::ORIGIN_Y),
                                                          GroundConstants::CELL_SIZE);
    size_t x_coord = hm_coords.first;
    size_t y_coord = hm_coords.second;

    std::vector<TVector3> o_changed_points;
    HeightMapUtils::GetPointsThatWillBeChanged(o_changed_points, i_height_map, x_coord, y_coord, x_coord, y_coord, i_direction, i_height_changed, visited);

    std::vector<TVector3> o_points_of_triangles_that_contain_changed_points;
    for(auto it : o_changed_points)
      {
      std::vector<TVector3> nearpoints;//top, righttop, right, bottom, leftbottom, left;

      auto coords = HeightMapUtils::FindHeightMapIndexes(TVector2(it.X(), it.Y()));
      size_t x = coords.first;
      size_t y = coords.second;

      if(x != 0)
        nearpoints.push_back(HeightMapUtils::ConvertHMapPointToTVector(x-1, y, i_height_map.Get(x-1, y)));
      if(x != 0 && y != i_height_map.GetSizeY()-1)
        nearpoints.push_back(HeightMapUtils::ConvertHMapPointToTVector(x-1, y+1, i_height_map.Get(x-1, y+1)));
      if(y != i_height_map.GetSizeY()-1)
        nearpoints.push_back(HeightMapUtils::ConvertHMapPointToTVector(x, y+1, i_height_map.Get(x, y+1)));
      if(x != i_height_map.GetSizeX()-1)
        nearpoints.push_back(HeightMapUtils::ConvertHMapPointToTVector(x+1, y, i_height_map.Get(x+1, y)));
      if(x != i_height_map.GetSizeX()-1 && y != 0)
        nearpoints.push_back(HeightMapUtils::ConvertHMapPointToTVector(x+1, y-1, i_height_map.Get(x+1, y-1)));
      if(y != 0)
        nearpoints.push_back(HeightMapUtils::ConvertHMapPointToTVector(x, y-1, i_height_map.Get(x, y-1)));

      o_points_of_triangles_that_contain_changed_points.push_back(it);
      for(auto near_it : nearpoints)
        o_points_of_triangles_that_contain_changed_points.push_back(near_it);
      }

    // deleting duplicates
    std::sort(o_points_of_triangles_that_contain_changed_points.begin(), o_points_of_triangles_that_contain_changed_points.end());
    auto last = std::unique(o_points_of_triangles_that_contain_changed_points.begin(), o_points_of_triangles_that_contain_changed_points.end());
    o_points_of_triangles_that_contain_changed_points.erase(last, o_points_of_triangles_that_contain_changed_points.end());

    GeneratePartOfSurface(o_changed_triangles, o_points_of_triangles_that_contain_changed_points, GroundConstants::ORIGIN_X, GroundConstants::ORIGIN_Y, GroundConstants::ORIGIN_Z, GroundConstants::CELL_SIZE);
    }
  }
